using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Threading;
using System.Security.Cryptography;
using System.Data;
//using MySql.Data.MySqlClient;

using OpenMetaverse;

namespace OpenSimCache
{
    public class IarFile
    {
		const int HeaderPriority = 0;
		const int InventoryPriority = 1;
		const int AssetPriority = 2;
		
        TarArchiveWriter _archive = new TarArchiveWriter();
		static int fileNameCounter = 0;
		Dictionary<UUID, byte[]> _uuidMd5Check = new Dictionary<UUID, byte[]>();
		Dictionary<byte[], UUID> _md5UuidCheck = new Dictionary<byte[], UUID>();
		const string ImportFolderName = "Import__b5f46048-9121-9e4a-6e3c-661886bedead/";
		public IarFile()
		{
            const string ARCHIVE_XML = "<?xml version=\"1.0\" encoding=\"utf-16\"?>\n<archive major_version=\"0\" minor_version=\"2\" />";

			// Create the archive.xml file
            _archive.AddFile(HeaderPriority, ArchiveConstants.CONTROL_FILE_PATH, ARCHIVE_XML);
		}

		public byte[] PackageArchive()
        {
			MemoryStream memBlock = new MemoryStream();
            _archive.WriteTar(new GZipStream(memBlock, CompressionMode.Compress));
			return memBlock.ToArray();
        }
		
		private byte[] buildInventoryElement(string name, UUID itemID, UUID assetID, InventoryType invType, AssetType assetType)
		{
			using( MemoryStream memory = new MemoryStream())
			{
                using (StreamWriter stream = new StreamWriter(memory))
                {
                    XmlTextWriter writer = new XmlTextWriter(stream);
					
					writer.WriteStartElement("InventoryItem");
					writer.WriteElementString("Name", name);
					writer.WriteElementString("ID", itemID.ToString());
					writer.WriteElementString("InvType", ((int)invType).ToString());
					writer.WriteElementString("CreatorUUID", "ospa:n=Snow Crash");
					writer.WriteElementString("CreationDate", "1282518830");
					writer.WriteElementString("Owner", "83918e9c-72e6-4ebd-9385-ae9a468d9c3c");
					writer.WriteElementString("Description", "(No Description)");
					writer.WriteElementString("AssetType", ((int)assetType).ToString());
					writer.WriteElementString("AssetID", assetID.ToString());
					writer.WriteElementString("SaleType", "0");
					writer.WriteElementString("SalePrice", "0");
					writer.WriteElementString("BasePermissions", "2147483647");
					writer.WriteElementString("CurrentPermissions", "2147483647");
					writer.WriteElementString("EveryOnePermissions", "0");
					writer.WriteElementString("NextPermissions", "2147483647");
					writer.WriteElementString("Flags", "0");
					writer.WriteElementString("GroupID", UUID.Zero.ToString());
					writer.WriteElementString("GroupOwned", "False");
					writer.WriteEndElement();
                    writer.Flush();
                }
            	return memory.ToArray();
			}
		}
		
		bool IsKnownTexture(UUID id)
		{
			if(id == OpenMetaverse.Primitive.TextureEntry.WHITE_TEXTURE)
				return true;
			if(id == new UUID("7ca39b4c-bd19-4699-aff7-f93fd03d3e7b"))	//hair.tga
			   return true;
			if(id == new UUID("6522e74d-1660-4e7f-b601-6f48c1659a77")) // eyes.tga
				return true;
			return false;
		}
		
		#if false
		bool IsAssetInDatabase(UUID assetID)
		{
			bool found = false;
			string connectionString =
		        		"Server=localhost;" +
		        		"Database=opensim;" +
		        		"User ID=root;" +
		        		"Password=Skeevej56;" +
		        		"Pooling=false";
		     
			IDbConnection dbcon;
		     		dbcon = new MySqlConnection(connectionString);
		     		dbcon.Open();
		     		IDbCommand dbcmd = dbcon.CreateCommand();
		     		string sql = "SELECT Id FROM assets WHERE id='" + assetID.ToString() + "'";
		     		dbcmd.CommandText = sql;
		     		IDataReader reader = dbcmd.ExecuteReader();
		     		while(reader.Read()) {
				found = true;
				break;
		     		}
		     		// clean up
		     		reader.Close();
		     		reader = null;
		     		dbcmd.Dispose();
		     		dbcmd = null;
		     		dbcon.Close();
		     		dbcon = null;
			return found;
		}
		#endif
		
		public void WriteAnimation(string name, UUID assetID, byte[] data)
		{
			// First check if this animation is already known, if so, igore it
			MD5CryptoServiceProvider md5Calc = new MD5CryptoServiceProvider();
			byte[] md5Hash = md5Calc.ComputeHash(data);
			if(_uuidMd5Check.ContainsKey(assetID))
			{
//				Console.WriteLine("Dropping " + assetID.ToString() + " Duplicate assetid");
				return;
			}
			if(_md5UuidCheck.ContainsKey(md5Hash))
			{
//				Console.WriteLine("Dropping " + assetID.ToString() + " Duplicate MD5 hash");
				return;
			}
			
//			if(IsAssetInDatabase(assetID))
//			{
//				Console.WriteLine("Dropping " + assetID.ToString() + " Duplicate found in asset db");
//				return;
//			}
			_md5UuidCheck[md5Hash] = assetID;
			_uuidMd5Check[assetID] = md5Hash;
			
			_archive.AddFile(AssetPriority, ArchiveConstants.ASSETS_PATH + name, data);
			
			UUID itemID = UUID.Random();
			string inventoryItemName = string.Format("Animation_{0}", ++fileNameCounter);			
			byte[] inventoryItemData = buildInventoryElement(inventoryItemName, itemID, assetID, InventoryType.Animation, AssetType.Animation);
			_archive.AddFile(InventoryPriority, ArchiveConstants.INVENTORY_PATH + ImportFolderName + inventoryItemName+"__" + itemID.ToString() + ".xml", inventoryItemData);
		}

		public void WriteClothing(string name, UUID assetID, byte[] data)
		{
			// first save the actual clothing item.
			_archive.AddFile(AssetPriority, ArchiveConstants.ASSETS_PATH + name, data);
			
			// parse the structure in order to determine the embedded textures
			OpenMetaverse.Assets.AssetClothing clothing = new OpenMetaverse.Assets.AssetClothing(assetID, data);
			if(clothing.Decode())
			{
				string inventoryItemName = clothing.Name;
				if(string.IsNullOrEmpty(inventoryItemName))
				   inventoryItemName = string.Format("Clothing_{0}", ++fileNameCounter);			
			
				foreach(UUID id in clothing.Textures.Values)
				{
					if(!IsKnownTexture(id))
					{
						try{
							byte[] j2kTexture = File.ReadAllBytes(Path.Combine(MainClass.jp2ExportPath, id.ToString() + ".jp2"));
							_archive.AddFile(AssetPriority, ArchiveConstants.ASSETS_PATH + id.ToString() + ArchiveConstants.ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Texture], j2kTexture);
							File.Delete(Path.Combine(MainClass.jp2ExportPath, id.ToString() + ".jp2"));
							File.Delete(Path.Combine(MainClass.tgaExportPath, id.ToString() + ".tga"));
						}
						catch
						{
							Console.WriteLine("Missing texture: " + id.ToString());
						}
					}
				}
			
				// Write the inventory item entry
				UUID itemID = UUID.Random();
				byte[] inventoryItemData = buildInventoryElement(inventoryItemName, itemID, assetID, InventoryType.Wearable, AssetType.Clothing);
				_archive.AddFile(InventoryPriority, ArchiveConstants.INVENTORY_PATH + ImportFolderName + inventoryItemName+"__" + itemID.ToString() + ".xml", inventoryItemData);
			}
			else
			{
				Console.WriteLine("Skipped: " + assetID.ToString());
			}
		}

		public void WriteBodyPart(string name, UUID assetID, byte[] data)
		{
			// first save the actual clothing item.
			_archive.AddFile(AssetPriority, ArchiveConstants.ASSETS_PATH + name, data);

			// parse the structure in order to determine the embedded textures
			OpenMetaverse.Assets.AssetBodypart bodypart = new OpenMetaverse.Assets.AssetBodypart(assetID, data);
			bodypart.Decode();
			
			string inventoryItemName = bodypart.Name;
			if(string.IsNullOrEmpty(inventoryItemName))
			   inventoryItemName = string.Format("bodypart_{0}", ++fileNameCounter);			
			
			foreach(UUID id in bodypart.Textures.Values)
			{
				if(!IsKnownTexture(id))
				{
					try{
						byte[] j2kTexture = File.ReadAllBytes(Path.Combine(MainClass.jp2ExportPath, id.ToString() + ".jp2"));
						_archive.AddFile(AssetPriority, ArchiveConstants.ASSETS_PATH + id.ToString() + ArchiveConstants.ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Texture], j2kTexture);
						File.Delete(Path.Combine(MainClass.jp2ExportPath, id.ToString() + ".jp2"));
						File.Delete(Path.Combine(MainClass.tgaExportPath, id.ToString() + ".tga"));
					}
					catch
					{
						Console.WriteLine("Missing texture: " + id.ToString());
					}
				}
			}
			
			// Write the inventory item entry
			UUID itemID = UUID.Random();
			byte[] inventoryItemData = buildInventoryElement(inventoryItemName, itemID, assetID, InventoryType.Wearable, AssetType.Bodypart);
			_archive.AddFile(InventoryPriority, ArchiveConstants.INVENTORY_PATH + ImportFolderName + inventoryItemName+"__" + itemID.ToString() + ".xml", inventoryItemData);
		}

		public void WriteGesture(string name, UUID assetID, byte[] data)
		{
//			// parse the structure in order to determine the embedded textures
//			OpenMetaverse.Assets.AssetGesture gesture = new OpenMetaverse.Assets.AssetGesture(assetID, data);
//			gesture.Decode();
//			
//			foreach(OpenMetaverse.Assets.GestureStep step in gesture.Sequence)
//			{
//				switch(step.GestureStepType)
//				{
//					case OpenMetaverse.Assets.GestureStepType.Animation:
//						OpenMetaverse.Assets.GestureStepAnimation anim = (OpenMetaverse.Assets.GestureStepAnimation)step;
//						Console.WriteLine("GestureAnimation: " + anim.Name + " " + anim.ID.ToString());
//						break;
//				}
//			}
		}
	}
}
